package com.android.kotlincoroutinemvvmdemo.shader
import android.opengl.GLES30
import android.opengl.GLSurfaceView
import android.util.Log
import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10

class ShapeSurfaceViewRenderer : GLSurfaceView.Renderer {

    private var drawIndex = 0

    private var uniformColor = 0

    private val VERTEX_SHADER = """#version 300 es
            layout(location=10) in vec4 a_Position;
            void main() {
                gl_Position = a_Position;
                gl_PointSize = 50.0;
            }
        """

    private val FRAGMENT_SHADER = """#version 300 es
            //定义所有浮点数据类型的默认精度；有lowp、mediump、highp三种，但只有部分硬件支持片段着色器使用highp。(顶点着色器默认highp)
            precision mediump float;
            //颜色是4分量，如果没有设置，则默认黑色 RGBA
            out vec4 u_Color;
            void main() {
                u_Color = vec4(0.0,1.0,1.0,1.0);
            }
        """

    /*定点的数据，只有一个点，就放中心即可*/
    private val POINT_DATA = floatArrayOf(0f, 0f)

    /*private val POINT_DATA = floatArrayOf(
        //x,y 一个点，这里相当于一个棱形，自己画个坐标，坐标是[-1,1]之间
        0f, 0f,
        0f, 0.5f,
        -0.5f, 0f,
        0f, -0.5f,
        0.5f, -0.5f,
        0.5f, 0f,
        0.5f, 0.5f,
    )*/

    private val vertexPoints = floatArrayOf(
        0.0f, 0.5f, 0.0f,
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f
    )

    /*Float类型占4Byte*/
    private val BYTES_PER_FLOAT = 4
    /*每个顶点数据关联的分量个数：当前案例只有x、y，故为2*/
    private val POSITION_COMPONENT_COUNT = 2

    /*通过nio ByteBuffer把设置的顶点数据加载到内存*/
    private var vertexData = ByteBuffer
        //分配顶点坐标分量个数 * Float占的Byte位数
        .allocateDirect(POINT_DATA.size * BYTES_PER_FLOAT)
        //按照本地字节序排序
        .order(ByteOrder.nativeOrder())
        //Byte类型转Float类型
        .asFloatBuffer()
        .put(POINT_DATA)
        //将缓冲区的指针指到头部，保证数据从头开始
        .position(0)

    private var vertexData2 = ByteBuffer
        //分配顶点坐标分量个数 * Float占的Byte位数
        .allocateDirect(vertexPoints.size * BYTES_PER_FLOAT)
        //按照本地字节序排序
        .order(ByteOrder.nativeOrder())
        //Byte类型转Float类型
        .asFloatBuffer()
        .put(vertexPoints)
        //将缓冲区的指针指到头部，保证数据从头开始
        .position(0)

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        //白色背景
        GLES30.glClearColor(1f, 1f, 1f, 1f)
        //编译着色器相关程序
        makeProgram(VERTEX_SHADER, FRAGMENT_SHADER)

        //关联顶点坐标属性和缓存数据，参数说明如下：
//        GLES30.glVertexAttribPointer(
//            0, //位置索引
//            POSITION_COMPONENT_COUNT,//用几个分量描述一个顶点
//            GLES30.GL_FLOAT,//分量类型
//            false,//固定点数据值是否应该被归一化
//            0,//指定连续顶点属性之间的偏移量。如果为0，那么顶点属性会被理解为：它们是紧密排列在一起的。初始值为0
//            vertexData2
//        ) //顶点数据缓冲区
//
//        //通知GL程序使用指定的顶点属性索引
//        GLES30.glEnableVertexAttribArray(0)
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        //填充整个页面
        GLES30.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(gl: GL10?) {
        //步骤1：使用glClearColor设置的颜色，刷新Surface
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

        //1.绘制的图形类型；2.从顶点数组读取的起点；3.从顶点数组读取的顶点个数,这里只绘制一个点
        //GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 1)

        //准备坐标数据
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexData2)
        //启用顶点的句柄
        GLES30.glEnableVertexAttribArray(0)
        //绘制三个点
        GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)
        //禁止顶点数组的句柄
        GLES30.glDisableVertexAttribArray(0)
    }

    /*编译着色器代码，获取代码Id*/
    private fun compileShader(type: Int, shaderCode: String): Int {
        //创建一个shader对象
        val shaderId = GLES30.glCreateShader(type)
        if (shaderId == 0) {
            Log.d(TAG, " 创建失败")
            return 0
        }
        //将着色器代码上传到着色器对象中
        GLES30.glShaderSource(shaderId, shaderCode)
        //编译对象
        GLES30.glCompileShader(shaderId)
        //获取编译状态，OpenGL把想要获取的值放入长度为1的数据首位
        val compileStatus = intArrayOf(1)
        GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, compileStatus, 0)
        Log.d(TAG, "compileShader: ${compileStatus[0]}")
        if (compileStatus[0] == 0) {
            Log.d(TAG, "编译失败: ${GLES30.glGetShaderInfoLog(shaderId)}")
            GLES30.glDeleteShader(shaderId)
            return 0
        }
        return shaderId
    }

    /*关联着色器代码，组成可执行程序*/
    private fun linkProgram(vertexShaderId: Int, fragmentShaderId: Int): Int {
        //创建一个OpenGL程序对象
        val programId = GLES30.glCreateProgram()
        if (programId == 0) {
            Log.d(TAG, "创建OpenGL程序对象失败")
            return 0
        }
        //关联顶点着色器
        GLES30.glAttachShader(programId, vertexShaderId)
        //关联片段周色漆
        GLES30.glAttachShader(programId, fragmentShaderId)
        //将两个着色器关联到OpenGL对象
        GLES30.glLinkProgram(programId)
        //获取链接状态,OpenGL把想要获取的值放入长度为1的数据首位
        val linkStatus = intArrayOf(1)
        GLES30.glGetProgramiv(programId, GLES30.GL_LINK_STATUS, linkStatus, 0)
        Log.d(TAG, "linkProgram: ${linkStatus[0]}")
        if (linkStatus[0] == 0) {
            GLES30.glDeleteProgram(programId)
            Log.d(TAG, "编译失败")
            return 0
        }
        return programId
    }

    /*生成可执行程序，并使用该程序*/
    private fun makeProgram(vertexShaderCode: String, fragmentShaderCode: String): Int {
        //需要编译着色器，编译成一段可执行的bin，去与显卡交流
        val vertexShader = compileShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
        //步骤2，编译片段着色器
        val fragmentShader = compileShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)

        //步骤3：将顶点着色器、片段着色器进行链接，组装成一个OpenGL程序
        val programId = linkProgram(vertexShader, fragmentShader)
        //链接之后就可以删除着色器对象了，不需要了
        GLES30.glDeleteShader(vertexShader)
        GLES30.glDeleteShader(fragmentShader)

        //通过OpenGL使用该程序
        GLES30.glUseProgram(programId)
        return programId
    }

    companion object {
        const val TAG = "ShapeSurfaceViewRenderer"
    }
}